/*
 * RT-Thread Secure
 *
 * Copyright (c) 2021, Shanghai Real-Thread Electronic Technology Co., Ltd.
 *
 * All rights reserved.
 */
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <string.h>

#if defined(RT_USING_SMART)
#include <ioremap.h>
#endif /* RT_USING_SMART */

struct rk3568_composite_pdata
{
    rt_ubase_t mux_reg;
    rt_uint32_t mux_shift;
    rt_uint32_t mux_width;
    rt_ubase_t gate_reg;
    rt_uint32_t gate_shift;
    rt_uint32_t gate_invert;
    rt_ubase_t divider_reg;
    rt_uint32_t divider_shift;
    rt_uint32_t divider_width;
    rt_bool_t divider_onebased;
};
typedef struct rk3568_composite_pdata rk3568_composite_pdata_t;

static rt_err_t set_parent(rt_clk_t *clk, rt_uint8_t index)
{
    rk3568_composite_pdata_t *pdata = (rk3568_composite_pdata_t *)clk->pdata;

    if(pdata->mux_reg == RT_NULL)
    {
        return (-RT_ERROR);
    }

    clrsetreg(pdata->mux_reg, (((1 << pdata->mux_width) - 1) << pdata->mux_shift), (index << pdata->mux_shift));

    return RT_EOK;
}

static rt_uint8_t get_parent(rt_clk_t *clk)
{
    rk3568_composite_pdata_t *pdata = (rk3568_composite_pdata_t *)clk->pdata;

    if(pdata->mux_reg == RT_NULL)
    {
        return 0;
    }

    return (readl(pdata->mux_reg) >> pdata->mux_shift) & ((1 << pdata->mux_width) - 1);
}

static void set_enable(struct rt_clk *clk, rt_bool_t enable)
{
    rk3568_composite_pdata_t *pdata = (rk3568_composite_pdata_t *)clk->pdata;
    rt_uint32_t value = 0;

    if(pdata->gate_reg == RT_NULL)
    {
        return;
    }

    if(enable)
    {
        value = (pdata->gate_invert ? 0x0 : 0x1) << pdata->gate_shift;
        value |= enable << (pdata->gate_shift + 16);
        writel(value, pdata->gate_reg);
    }
    else
    {
        value = (pdata->gate_invert ? 0x1 : 0x0) << pdata->gate_shift;
        setreg(pdata->gate_reg, value);
    }
}

static rt_bool_t get_enable(struct rt_clk *clk)
{
    rk3568_composite_pdata_t *pdata = (rk3568_composite_pdata_t *)clk->pdata;

    if(pdata->gate_reg == RT_NULL)
    {
        return RT_TRUE;
    }

    if(readl(pdata->gate_reg) & (0x1 << pdata->gate_shift))
    {
        return pdata->gate_invert ? RT_FALSE : RT_TRUE;
    }

    return pdata->gate_invert ? RT_TRUE : RT_FALSE;
}

static void set_rate(rt_clk_t *clk, rt_uint64_t parent_rate, rt_uint64_t rate)
{
    rk3568_composite_pdata_t *pdata = (rk3568_composite_pdata_t *)clk->pdata;
    rt_uint32_t mask = ((1 << (pdata->divider_width)) - 1);
    rt_uint32_t div = 0;

    if(pdata->divider_reg == RT_NULL)
    {
        return;
    }

    if(rate == 0)
    {
        rate = parent_rate;
    }

    div = parent_rate / rate;
    if(pdata->divider_onebased)
    {
        div--;
    }

    if(div > mask)
    {
        div = mask;
    }

    clrsetreg(pdata->divider_reg, mask << pdata->divider_shift, div << pdata->divider_shift);
}

static rt_uint64_t get_rate(rt_clk_t *clk, rt_uint64_t parent_rate)
{
    rk3568_composite_pdata_t *pdata = (rk3568_composite_pdata_t *)clk->pdata;
    rt_uint32_t mask = ((1 << (pdata->divider_width)) - 1);
    rt_uint32_t div = 0;

    if(pdata->divider_reg == RT_NULL)
    {
        return parent_rate;
    }

    div = (readl(pdata->divider_reg) >> pdata->divider_shift);
    div &= mask;

    if(pdata->divider_onebased)
    {
        div++;
    }

    if(div == 0)
    {
        div = 1;
    }

    return parent_rate / div;
}

static rt_uint64_t get_round_rate(rt_clk_t *clk, rt_uint64_t parent_rate, rt_uint64_t rate)
{
    /* TODO */
    return 0ULL;
}

static struct rt_clk_ops ops =
{
    .set_enable = set_enable,
    .get_enable = get_enable,
    .set_parent = set_parent,
    .get_parent = get_parent,
    .set_rate = set_rate,
    .get_rate = get_rate,
    .get_round_rate = get_round_rate,
};

rt_clk_t *rt_clk_rk3568_composite_register(const char *name, const char *parents,
    rt_ubase_t mux_reg, rt_uint32_t mux_shift, rt_uint32_t mux_width,
    rt_ubase_t gate_reg, rt_uint32_t gate_shift, rt_uint32_t gate_invert,
    rt_ubase_t divider_reg, rt_uint32_t divider_shift, rt_uint32_t divider_width, rt_bool_t divider_onebased)
{
    rt_clk_t *clk = RT_NULL;
    rk3568_composite_pdata_t *pdata = RT_NULL;
    rt_uint32_t len = 0;
    char *parents_copy = RT_NULL;
    char *parent = RT_NULL;
    rt_uint32_t parent_num = 0;
    rt_uint32_t index = 0;
    char *value = RT_NULL;

    if (!parents)
    {
        goto _fail;
    }

    if((len = rt_strlen(parents)) == 0)
    {
        goto _fail;
    }

    parents_copy = rt_strdup(parents);
    if (parents_copy == RT_NULL)
    {
        goto _fail;
    }

    parent = parents_copy;
    while (rt_strsep(&parent, RT_CLK_MUX_DELIM))
    {
        parent_num++;
    }

    clk = rt_clk_create(name, &ops, parent_num, sizeof(rk3568_composite_pdata_t), 0);
    if(!clk)
    {
        goto _fail;
    }

    /* 处理父时钟字符串 */
    for (parent = parents_copy; (parent < (parents_copy + len)); index++)
    {
        value = parent;
        parent += rt_strlen(parent);
        for (parent += rt_strlen(parent); ((parent < (parents_copy + len)) && (!*parent)); parent++);
        clk->parents[index] = rt_clk_find(value);
    }

    pdata = (rk3568_composite_pdata_t *)clk->pdata;
#if defined(RT_USING_SMART)
    if(mux_reg)
    {
        pdata->mux_reg = (rt_ubase_t)rt_ioremap((void *)mux_reg, 4096);
    }
    if(gate_reg)
    {
        pdata->gate_reg = (rt_ubase_t)rt_ioremap((void *)gate_reg, 4096);
    }
    if(divider_reg)
    {
        pdata->divider_reg = (rt_ubase_t)rt_ioremap((void *)divider_reg, 4096);
    }
#else
    pdata->mux_reg = mux_reg;
    pdata->gate_reg = gate_reg;
    pdata->divider_reg = divider_reg;
#endif /* RT_USING_SMART */
    pdata->mux_shift = mux_shift;
    pdata->mux_width = mux_width;
    pdata->gate_shift = gate_shift;
    pdata->gate_invert = gate_invert;
    pdata->divider_shift = divider_shift;
    pdata->divider_width = divider_width;
    pdata->divider_onebased = divider_onebased;

    if (rt_clk_register(clk) != RT_EOK)
    {
        goto _fail;
    }

    rt_free(parents_copy);
    parents_copy = RT_NULL;

    return clk;

_fail:
    if(parents_copy)
    {
        rt_free(parents_copy);
        parents_copy = RT_NULL;
    }

    if(clk)
    {
        rt_free(clk);
        clk = RT_NULL;
    }

    return RT_NULL;
}

rt_err_t rt_clk_rk3568_composite_unregister(rt_clk_t *clk)
{
    rt_err_t ret = RT_EOK;

    ret = rt_clk_unregister(clk);
    if(!ret)
    {
        return ret;
    }

    rt_clk_delete(clk);

    return RT_EOK;
}
